package cn.nawang.ebeim.client;

import java.io.File;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import cn.nawang.ebeim.client.bean.BackStrategy;
import cn.nawang.ebeim.client.bean.ClientConfiguration;
import cn.nawang.ebeim.client.bean.DataSource;
import cn.nawang.ebeim.client.bean.DataSourceQueue;
import cn.nawang.ebeim.client.bean.DataSourceTask;
import cn.nawang.ebeim.client.bean.License;
import cn.nawang.ebeim.client.bean.StorageServer;
import cn.nawang.ebeim.client.bean.TaskStatusBean;
import cn.nawang.ebeim.client.bean.UploadProcessBarBean;
import cn.nawang.ebeim.client.exception.ApiRequestException;
import cn.nawang.ebeim.client.exception.AuthenticationException;
import cn.nawang.ebeim.client.exception.InitializationException;
import cn.nawang.ebeim.client.exception.RootException;
import cn.nawang.ebeim.client.util.ApiUtil;
import cn.nawang.ebeim.client.util.ApiVisitUtil;
import cn.nawang.ebeim.client.util.CommonMethodsUtils;
import cn.nawang.ebeim.client.util.Constants;
import cn.nawang.ebeim.client.util.DbUpDownloadFailedUtil;
import cn.nawang.ebeim.client.util.DbUpDownloadUtil;
import cn.nawang.ebeim.client.util.DbUtil;
import cn.nawang.ebeim.client.util.DealUnfinishTask;
import cn.nawang.ebeim.client.util.FileUtil;
import cn.nawang.ebeim.client.util.LicenseVerifyUtil;
import cn.nawang.ebeim.client.util.MACAddress;
import cn.nawang.ebeim.client.util.NetWorkMonitorDbUtil;
import cn.nawang.ebeim.client.util.PropertiesConstants;
import cn.nawang.ebeim.client.util.PropertiesUtil;
import cn.nawang.ebeim.client.util.RunningDsIds;
import cn.nawang.ebeim.client.util.ThreadPoolUtil;
import cn.nawang.ebeim.client.util.UploadProcessBarList;

public class AppMain {
  private static final Logger LOG = LoggerFactory.getLogger(AppMain.class);

  public static AtomicBoolean stopFlag = new AtomicBoolean(false);

  public AppMain() {}

  /**
   * 主线程，负责验证授权、初始化等工作
   * 
   * @throws InitializationException
   */
  public void run() throws AuthenticationException, InterruptedException, InitializationException {
    LOG.info("AppMain starts to run at {}", System.currentTimeMillis());

    ClientConfiguration.setTerminalId(MACAddress.getMACAddr());
    String workingPath = ClientConfiguration.getWorkingPath();
    FileUtil.choosePath(workingPath, PropertiesConstants.WORKING_PATH, FileUtil.PATH_WORKING);

    String customerId = null;
    while ((customerId = PropertiesUtil.read(PropertiesConstants.CUSTOMER_ID)) == null) {
      LOG.info("waiting to read customerId........");
      Thread.sleep(1000 * 10);
    }
    LOG.info("read customerId [" + customerId + "] OK........");
    ClientConfiguration.setCustomerId(customerId);


    /** 初始化 */
    init();
    LOG.info("init OK....");
    /** 处理上次未完成的任务 */
    processUnfinishedTask();
    LOG.info("processUnfinishedTask OK...");

    /** 处理定期任务 */
    processScheduledTask();

  }

  public void processUnfinishedTask() {
    List<DataSource> dataSources = getDataSources(ClientConfiguration.getTerminalId());
    for (DataSource ds : dataSources) {
      if (!new File(ds.getRoot()).exists() && ds.getDsType().equals(Constants.DS_TYPE_FILE)) {
        ApiUtil.updateDataSourceStatus(ds, Constants.DS_RUN_STATUS_ERROR);
        // 日志处理
        CommonMethodsUtils.backupLogProcess(ds.getId(), ds.getNextVersion().getMajorVersion(),
            ds.getNextVersion().getMinorVersion(), Constants.BACKUPLOG_STATUS_FAILED, 0l,
            "数据源目录:[" + ds.getRoot() + "]不存在");
        continue;
      }
      if (ds.getStatus() == Constants.DS_RUN_STATUS_RUNNING) {
        List<TaskStatusBean> beans = DbUtil.getTaskStatusUnFinished(ds.getId());
        // 数据库版本号更新了，但是没有上传版本树，因此版本号减一,备份次数减一
        if ((beans == null || beans.size() == 0)
            && (ds.getCurrentVersionMajor() != 0 && ds.getCurrentVersionMinor() != 0)) {
          DataSource datasource = new DataSource();
          datasource.setId(ds.getId());
          datasource.setCurrentVersionMajor(ds.getCurrentVersionMajor() - 1);
          datasource.setCurrentVersionMinor(ds.getCurrentVersionMinor());
          datasource.setCount(ds.getCount() - 1);
          ApiVisitUtil.updataDatasource(datasource);
        } else {
          // 将dsId添加到全局变量
          RunningDsIds.addId(ds.getId());
          UploadProcessBarBean bean = DbUpDownloadUtil.getUploadProcessBar(ds.getId(),
              DbUpDownloadUtil.PROCESS_BAR_STATUS_RUNNING);
          if (bean != null) {
            UploadProcessBarList.total.put(bean.getDsId(), bean.getCount());
          }

        }
        for (TaskStatusBean taskStatusBean : beans) {
          new DealUnfinishTask().doUnfinishedTask(taskStatusBean, ds.getIsEncrypt());
        }
        // 是否还有数据文件没有上传
        int dataCount = DbUtil.getTaskStatusFileUnfinishedCount(DbUtil.TASK_TYPE_FILE, ds.getId());
        if (dataCount == 0) {
          ApiUtil.updateDataSourceStatus(ds, Constants.DS_RUN_STATUS_SUCCESS);
          // 日志处理
          CommonMethodsUtils.backupLogProcess(ds.getId(), ds.getNextVersion().getMajorVersion(),
              ds.getNextVersion().getMinorVersion(), Constants.BACKUPLOG_STATUS_SUCCESS, 0l,
              "备份成功!");
        } else {
          ApiUtil.updateDataSourceStatus(ds, Constants.DS_RUN_STATUS_ERROR);
          // 日志处理
          CommonMethodsUtils.backupLogProcess(ds.getId(), ds.getNextVersion().getMajorVersion(),
              ds.getNextVersion().getMinorVersion(), Constants.BACKUPLOG_STATUS_FAILED, 0l,
              "备份异常!");
          DbUtil.deleteTaskStatusFile(ds.getId());
          DbUtil.deleteTaskStatusVersion(ds.getId(), ds.getCurrentVersion());
        }
      }
    }
  }

  public void processScheduledTask() {
    while (!AppMain.stopFlag.get()) {
      /** 获取数据源列表 */
      String macAddr = ClientConfiguration.getTerminalId();
      List<DataSource> dataSources = getDataSources(macAddr);
      /** 遍历数据源,将需要备份的数据源加入备份队列 */
      for (DataSource ds : dataSources) {
        initWorkingDirs(ds); /* 其实不用每次都初始化目录，之所以如此，是为了防止新添加的数据源的工作目录没有初始化, 应该优化 */
        if (needBackup(ds)) {
          DataSourceTask task = new DataSourceTask(ds);
          enqueueDataSource(task);
        }
      }

      /** 等待一定时间再次去检查数据源 */
      try {
        Thread.sleep(ClientConfiguration.getDatasourceCheck());
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
    }
  }


  /** 进行授权验证 */
  public void authenticate() throws AuthenticationException {
    LOG.debug("TODO - authenticate");
    boolean flag = false;
    try {
      License license = LicenseVerifyUtil.parseLicenseInfo();
      // 判断授权信息是否存在
      if (license == null) {
        flag = true;
      } else {
        System.out.println(license);
        ClientConfiguration.setTerminalId(license.getTerminalID());
        ClientConfiguration.setCustomerId(license.getCustomID());
      }
      if (flag) {
        throw new AuthenticationException();
      }
    } catch (RootException e) {
      e.printStackTrace();
      throw new AuthenticationException();
    }
  }

  /**
   * 初始化工作目录、启动处理线程等
   * 
   * @throws InitializationException
   */
  public void init() throws InterruptedException, InitializationException {

    List<StorageServer> servers = CommonMethodsUtils.getServer();
    while (true) {
      if (servers == null || servers.size() == 0) {
        Thread.sleep(ClientConfiguration.getServerRetriesSleep());
        servers = CommonMethodsUtils.getServer();
      } else {
        break;
      }
    }

    ClientConfiguration.setStorageServers(servers);
    /** 启动备份、恢复和上传线程池 */
    ThreadPoolUtil.startDataSourceThreads();
    ThreadPoolUtil.startBackupThreads();
    ThreadPoolUtil.startUploadThreads();
    ThreadPoolUtil.startRecoveryThreads();
    ThreadPoolUtil.startLocalBackupThreads();
    /** 初始化任务队列，用于与C#客户端通信 */
    try {
      File workingDir = new File(ClientConfiguration.getWorkingPath());
      if (!workingDir.exists()) {
        workingDir.mkdirs();
      }
      DbUtil.initConnection();
      DbUtil.initDatabase();

      DbUpDownloadUtil.initConnection();
      DbUpDownloadUtil.initDatabase();
      
      DbUpDownloadFailedUtil.initConnection();
      DbUpDownloadFailedUtil.initDatabase();

      NetWorkMonitorDbUtil.initConnection();
      NetWorkMonitorDbUtil.initDatabase();
    } catch (SQLException e) {
      LOG.error("init database failed", e);
      throw new InitializationException("init task queue database failed");
    }
    ThreadPoolUtil.startTaskFetcherThreads();

    // 任务执行情况处理
    new Thread(new TaskStatusThread()).start();
    // 文件下载进度条线程
    new Thread(new DownloadProcessBarThread()).start();
    // 文件上传进度条线程
    new Thread(new UploadProcessBarThread()).start();
    // 心跳线程
    new Thread(new HeartLogThread()).start();
    // 断点续传
    new Thread(new BreakPointThread()).start();
    // 用户重新上传下载失败的文件
    new Thread(new UpDownloadFailedThread()).start();
    
    
    
    // 网络性能监控线程
     new Thread(new NetWorkMonitorThread()).start();
  }

  /** 初始化数据源工作目录 */
  public void initWorkingDirs(DataSource ds) {
    FileUtil.initWorkingPath(ds.getId());
  }

  /** 从API获取该terminal上的数据源列表 */
  public List<DataSource> getDataSources(String terminalId) {
    LOG.debug("getDataSources for terminal: {}", terminalId);
    List<DataSource> list = null;
    try {
      list = ApiUtil.getDataSources(terminalId);
    } catch (ApiRequestException e) {
      LOG.error("fail to getDataSources:{}", e);
      e.printStackTrace();
    }
    if (list == null) {
      list = new ArrayList<DataSource>();
    }
    return list;
  }

  /**
   * @author 张建峰
   * @Description: 判断备份策略中的备份时间是否被修改
   * @param @param ds
   * @return void
   * @date 2015年10月9日下午2:41:54
   *
   * @修改人 张建峰
   * @修改日期
   */
  public void checkBackupTime(DataSource ds, BackStrategy bs) {

    // 判断ds的backstragry的时间是否修改，若修改，则ds.setNextRun()
    if (ds.getNextRun() == 0 || bs.getBackupTimeStatus() == Constants.BACKUP_TIME_UPDATE) {
      // nextRun设置为用户设定的时间
      ds.setNextRun(bs.getBackupTime() == null ? 0 : bs.getBackupTime());

      DataSource newDs = new DataSource();
      newDs.setId(ds.getId());
      newDs.setNextRun(ds.getNextRun());
      ApiVisitUtil.updataDatasource(newDs);
      try {
        ApiUtil.updateBackStrategyStatus(bs.getBsId(), Constants.BACKUP_TIME_NOT_UPDATE);
      } catch (ApiRequestException e) {
        // TODO Auto-generated catch block
        LOG.error("fail to updateBackStrategyStatus:{}", e);
        e.printStackTrace();
      }
    }
  }

  /** 判断该数据源是否需要备份,目前的策略是到达备份时间，并且不是正在运行 */
  public boolean needBackup(DataSource ds) {
    LOG.debug("needBackup: {}", ds);
    if (ds.getDsType().equals(Constants.DS_TYPE_TREE)
        || ds.getDsType().equals(Constants.DS_TYPE_SAFEBOX)) {
      return false;
    }
    BackStrategy bs = null;
    try {
      bs = ApiUtil.getBackStrategyByDsId(ds.getId());
    } catch (ApiRequestException e1) {
      // TODO Auto-generated catch block
      LOG.error("fail to getBackStrategyByDsId:{}", e1);
      e1.printStackTrace();
    }
    if (bs == null) {
      return false;
    }
    checkBackupTime(ds, bs);
    if (ds.getStatus() != Constants.DS_RUN_STATUS_RUNNING
        && ds.getDsStatus() != Constants.DS_STATUS_NOUSE
        && ds.getNextRun() < System.currentTimeMillis()) {

      CommonMethodsUtils.addBackupLogProcess(ds, "自动备份");

      if (!new File(ds.getRoot()).exists() && ds.getDsType().equals(Constants.DS_TYPE_FILE)) {
        ApiUtil.updateDataSourceStatus(ds, Constants.DS_RUN_STATUS_ERROR);
        // 日志处理
        CommonMethodsUtils.backupLogProcess(ds.getId(), ds.getNextVersion().getMajorVersion(),
            ds.getNextVersion().getMinorVersion(), Constants.BACKUPLOG_STATUS_FAILED, 0l,
            "数据源目录:[" + ds.getRoot() + "]不存在");
        return false;
      }
      
      if(ClientConfiguration.isRunningList.get(ds.getId())!=null){
        return false;
      }
      ClientConfiguration.isRunningList.put(ds.getId(), ds);

      DataSource newDs = new DataSource();
      newDs.setId(ds.getId());

      int newCount = (ds.getCount() == null ? 1 : ds.getCount() + 1);
      // 成功备份次数加1
      newDs.setCount(newCount);
      // 版本号
      newDs.setCurrentVersionMajor(ds.getNextVersion().getMajorVersion());
      newDs.setCurrentVersionMinor(ds.getNextVersion().getMinorVersion());
      // 执行状态
      newDs.setStatus(Constants.DS_RUN_STATUS_RUNNING);

      ApiVisitUtil.updataDatasource(newDs);
      ds = ApiUtil.getDataSource(ds.getId());
      if (ds != null) {
        calCulateNextRun(ds, bs);
        return true;
      }
    }
    return false;
  }

  /**
   * @author 张建峰
   * @Description: 计算下次备份时间
   * @param @param ds
   * @return void
   * @date 2015年8月25日上午9:15:00
   *
   * @修改人 张建峰
   * @修改日期
   */
  public void calCulateNextRun(DataSource ds, BackStrategy bs) {
    byte backupPeriod = Byte.valueOf(bs.getBackupPeriod());
    int backupCycle = bs.getBackupCycle();

    long f = 1000 * 60 * 60 * backupCycle;

    // Date date = new Date(ds.getNextRun());
    // Calendar calendar = new GregorianCalendar();
    // calendar.setTime(date);
    if (backupPeriod == Constants.BACKUP_PERIOD_DAY) {
      // 把日期往后增加backupCycle天.整数往后推
      // calendar.add(Calendar.DATE, backupCycle);
      f = f * 24;
    } else if (backupPeriod == Constants.BACKUP_PERIOD_HOUR) {
      // 把日期往后增加backupCycle小时.整数往后推
      // calendar.add(Calendar.HOUR, backupCycle);
      f = f * 1;
    } else if (backupPeriod == Constants.BACKUP_PERIOD_WEEK) {
      // 把日期往后增加backupCycle小时.整数往后推
      // calendar.add(Calendar.DATE, backupCycle*7);
      f = f * 24 * 7;
    } else if (backupPeriod == Constants.BACKUP_PERIOD_MONTH) {
      // 把日期往后增加backupCycle小时.整数往后推
      // calendar.add(Calendar.MONTH, backupCycle);
      f = f * 24 * 30;
    }

    long nr = ds.getNextRun() + (((new Date().getTime() - ds.getNextRun()) / f) + 1) * f;

    // date = calendar.getTime();
    // ds.setNextRun(date.getTime());
    DataSource newDs = new DataSource();
    newDs.setId(ds.getId());
    newDs.setNextRun(nr);
    ApiVisitUtil.updataDatasource(newDs);
  }

  public void enqueueDataSource(DataSourceTask task) {
    LOG.debug("enqueue datasource task: {}", task);
    try {
      DataSourceQueue.enqueue(task);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
  }

  
  public static void exitClient(){
    System.exit(0);
  }
  
  
  public static void main(String[] args) throws Exception {
    cn.nawang.ebeim.client.util.SystemUtil.initLog();
    LOG.debug("AppMain starts");
    AppMain app = new AppMain();
    try {
      app.run();
    } catch (Exception e) {
      e.printStackTrace();
      LOG.error(e.getMessage());
    }
  }

}
